Skip to content

Toolchain, board and the master template

Companion videos

Watch on YouTube (opens in a new tab)

Videos by Thai Embedded Systems Association (TESA) · The whole series in the playlist AIoT Foundation

  1. Install ModusToolbox, clone the master template and build it without errors
  2. Drop an episode into proj_cm55/apps/ and flash it through KitProg3
  3. Explain the roles of proj_cm33_s, proj_cm33_ns and proj_cm55 on the dual-core chip
  • TESAIoT Dev Kit (the PSoC Edge AI Kit SoM on the QWA309 base board) and a USB-C cable for KitProg3
  • Infineon ModusToolbox 3.6 or later, as the master template README says (the episode series README recommends 3.8)
  • git and a computer that can build C (Windows, macOS or Linux)

The PSoC Edge E84 is a dual-core chip (Arm Cortex-M55 + Cortex-M33), and ModusToolbox splits a single board’s firmware into three projects, each with its own main.c, built and flashed separately but coordinating at boot.

Project Core Libraries in deps/ Role
proj_cm33_s CM33 (secure) none (just assetlocks.json) Configures MPC/PPC so some memory and peripheral regions become non-secure, then jumps to the non-secure side’s reset handler
proj_cm33_ns CM33 (non-secure) none (just assetlocks.json) Starts the CM55 core at its boot address, then enters deep sleep forever
proj_cm55 CM55 15 packages — lvgl, freertos, wifi-core-freertos-lwip-mbedtls, 3 display drivers, 3 touch drivers, 4 sensor drivers Runs the entire real application: the LVGL screen, the VGLite GPU, sensors, Wi-Fi, the microphone and the episode’s code

The deps/ listing confirms what the master’s README says: proj_cm33_s and proj_cm33_ns link no middleware at all — their only job is to “wake up the next core”. The display work, the Wi-Fi work and the sensor bus of every episode (including the Wi-Fi manager in module 2) all live in proj_cm55. A project outside this series may split the work differently, so always check the deps/ folder and main.c of each project rather than assuming.

The code excerpts in this section are copied from tesaiot/developer-hub (Apache-2.0) at commit 082fd3e

proj_cm33_ns/main.c is very short, because it has exactly one job:

/* Enable CM55. */
/* CM55_APP_BOOT_ADDR must be updated if CM55 memory layout is changed.*/
Cy_SysEnableCM55(MXCM55, CM55_APP_BOOT_ADDR, CM55_BOOT_WAIT_TIME_USEC);
/* Enable global interrupts */
__enable_irq();
/* Put the CPU to Deep Sleep */
for (;;)
{
Cy_SysPm_CpuEnterDeepSleep(CY_SYSPM_WAIT_FOR_INTERRUPT);
}

CM55_APP_BOOT_ADDR is where the proj_cm55 firmware sits in flash, and CM55_BOOT_WAIT_TIME_USEC (10 microseconds) is how long it waits for the CM55 core to boot before continuing. After that, proj_cm33_ns does nothing else but sleep in deep sleep — all of the episode’s work falls to proj_cm55.

proj_cm55: the sequence that readies the system before our code runs

Section titled “proj_cm55: the sequence that readies the system before our code runs”

main() in proj_cm55 creates a FreeRTOS task called cm55_gfx_task and starts the scheduler. That task runs, in order: init the GFX subsystem (Cy_GFXSS_Init) → set up the Display Controller and GPU interrupts → init the display/touch I2C bus → init the display for whichever panel is selected in common.mk → init the sensor bus, best-effort → init memory and VGLite → lv_init() + lv_port_disp_init() + lv_port_indev_init(), and only then call the episode’s code as the final step:

/* Initialize LVGL library */
lv_init();
lv_port_disp_init();
/* Initialize touch input for interactive UI controls. */
lv_port_indev_init();
/* ========================================================= *
* EPISODE ENTRY POINT — master template invariant *
* ========================================================= *
* main.c NEVER changes per episode. Whatever episode's code *
* currently lives in proj_cm55/apps/ provides a strong *
* implementation of example_main(parent). *
* *
* When apps/ is empty, the weak stub in *
* apps/_default/example_main_default.c takes over and *
* shows instructions for downloading an episode. *
* ========================================================= */
lv_obj_t *parent = lv_scr_act();
example_main(parent);

example_main(parent) is called exactly once, after every subsystem is ready — parent is the active screen (lv_scr_act()) that the episode uses as the starting point for its own object tree. The episode’s code therefore must not (and should not) call lv_init() or re-init the display/touch.

Sensor bus init is best-effort: on failure it printfs a warning and lets boot continue, it does not assert and halt the whole system, because the master has no way to know in advance which sensors the episode that is about to run will need:

/* Initialize dedicated sensor buses. Best-effort — episodes that do not
* use sensors will simply ignore these handles. Master template always
* initializes ALL subsystems so any episode dropped in apps/ Just Works. */
cy_rslt_t sensor_i2c_rslt = sensor_i2c_controller_init();
if (CY_RSLT_SUCCESS != sensor_i2c_rslt)
{
printf("[MASTER] Sensor I2C init failed (0x%08lx) — sensor episodes unavailable\r\n",
(unsigned long)sensor_i2c_rslt);
}
cy_rslt_t i3c_rslt = i3c_controller_init();
if (CY_RSLT_SUCCESS != i3c_rslt)
{
printf("[MASTER] I3C init failed (0x%08lx) — BMM350 compass episodes unavailable\r\n",
(unsigned long)i3c_rslt);
}

I2C serves DPS368 (pressure), SHT4x (temperature/humidity) and BMI270 (6-axis IMU); I3C serves only BMM350 (magnetometer/compass). If a module-3 episode cannot read any sensor at all from the start, check this log line first.

The contract between the master and an episode: example_main as weak/strong

Section titled “The contract between the master and an episode: example_main as weak/strong”

proj_cm55/apps/app_interface.h declares the contract every episode must follow:

#include "app_interface.h"
/* Episode MUST provide a strong definition: */
void example_main(lv_obj_t *parent);

The master has a weak-symbol example_main() in apps/_default/example_main_default.c as the default (it draws the “no episode installed” card). Once we drop an episode’s files that declare example_main() as strong into proj_cm55/apps/, the linker automatically picks the episode’s version instead — main.c calls example_main(parent) exactly the same way every time, so the master’s code never needs to change no matter how many episodes you swap through. This is why item 2 says “never delete app_interface.h” — remove it and the episode fails to compile, because there is no prototype left for it to match.

The apps/ folder and installing an episode

Section titled “The apps/ folder and installing an episode”

Every episode downloaded from the Developer Hub is designed so that all of its files go into the single proj_cm55/apps/ folder, with no Makefile edits needed, because the master’s INCLUDES uses find ./apps -type d to auto-discover whatever subfolders the episode creates. Two system files must always stay alongside it: app_interface.h (the contract above) and _default/ (the placeholder screen shown before an episode is installed). The master’s tools/install_episode.sh script automates three steps in one command: delete the old episode’s files (keeping the two system files), rsync the new episode’s files in, and clear the apps/ build cache so the next build is clean.

What proj_cm55 has ready before an episode starts

Section titled “What proj_cm55 has ready before an episode starts”

By the time example_main(parent) is called, these subsystems are already usable without any init of your own — LVGL 9 with its full widget set and Montserrat fonts sized 12–40, the VGLite GPU accelerating every draw call, a touch controller (GT911/FT5406/ILI2511 depending on the display) already bound to LVGL, the I2C sensor bus (1.8V domain) and I3C bus described above, a stereo PDM microphone, the WiFi Connection Manager (cy_wcm) with lwIP and mbedTLS for TLS, FreeRTOS with tickless idle for power saving, and printf() going out the debug UART at 115200 baud.

  • Build hangs in proj_cm33_s with schema cydesignfile_v7 not found — happens when someone opens design.modus with the Device Configurator from ModusToolbox 3.7 or later and saves over it, upgrading the original schema (v6) to v7, which a machine on ModusToolbox 3.6 cannot read. Never edit and save design.modus with a newer configurator.
  • duplicate symbol example_main at link time — more than one file under apps/** declares void example_main(lv_obj_t *parent) as strong (for example, forgetting to delete the previous episode’s files before dropping in a new one). Fix by deleting the duplicate file; a build can have only one strong example_main.
  • multiple definition of APP_LOGO — some episodes ship their own app_logo.c/app_logo.h even though the master already provides app_assets/app_logo.c. Delete the episode’s logo files and #include "app_logo.h" as usual.
  • Sensor I2C/I3C init fails silently — a failure just printfs one warning line and boot continues; it does not assert and halt. If a sensor reads nothing at all from the start, open the serial log at 115200 baud and look for [MASTER] Sensor I2C init failed or [MASTER] I3C init failed before chasing other causes.
  1. Clone the master template from the tesaiot_dev_kit_master branch of the Developer Hub

    Terminal window
    git clone -b tesaiot_dev_kit_master https://github.com/tesaiot/developer-hub.git tesaiot_dev_kit_master
    cd tesaiot_dev_kit_master
    make getlibs
  2. Build and flash following the steps in the master template README

Terminal window
# In the master template folder (see lesson 1.1)
# 1) Delete the old episode's files in proj_cm55/apps/
# 2) Copy all of this episode's files into proj_cm55/apps/
make build
make program # flash through KitProg3
  1. If you are not ready to build it yourself yet, open the example on the Developer Hub and flash the ready-made firmware from the example page (every episode in modules 2–3 and every QWA309 practice code in module 4 has ready-to-use firmware)
  • Where must an episode’s files go, and which files in that folder must you never delete?
  • In the master template, the display work and the Wi-Fi work both live in proj_cm55. So what does proj_cm33_ns do, and what would you gain and lose by moving the network work to the other core?
  • make build succeeds but make program cannot find the board. What should you check first?

Review questions

Answer on your own first, then open the answer.

  1. After make getlibs, make build stops in proj_cm33_s saying schema cydesignfile_v7 is not found. What is the most likely cause? (Objective 1)

    1. ยังไม่ได้วางไฟล์ episode ลงใน proj_cm55/apps/
    2. ไฟล์ design.modus ของ BSP ถูกเปิดแล้วบันทึกด้วย Device Configurator รุ่นใหม่กว่า (3.7) ขณะที่เครื่องใช้ ModusToolbox 3.6 schema จึงถูกยกเป็น v7
    3. สาย USB ของ KitProg3 ยังไม่ได้เสียบ
    4. make getlibs ดาวน์โหลด LVGL มาไม่ครบ
    Show answer

    Answer: B. ไฟล์ design.modus ของ BSP ถูกเปิดแล้วบันทึกด้วย Device Configurator รุ่นใหม่กว่า (3.7) ขณะที่เครื่องใช้ ModusToolbox 3.6 schema จึงถูกยกเป็น v7

    docs/EXTENDING.md ของ master template ข้อ 4.6 อธิบายว่า BSP ที่ถูกบันทึกด้วย ModusToolbox 3.7 จะใช้ schema v7 ซึ่งเครื่องที่ใช้ 3.6 อ่านไม่ได้ ให้ใช้ BSP schema v6 ที่มากับ template และอย่าเปิดแล้วเซฟ design.modus ด้วย configurator รุ่นใหม่กว่า การยังไม่มี episode ไม่ทำให้ build ล้ม (จะได้หน้าจอ default แทน) และ KitProg3 เกี่ยวกับขั้น make program ไม่ใช่ make build

  2. Build and flash succeed, but the screen shows a “Master Template Ready” card saying no episode is installed. What does this mean? (Objective 2)

    1. บอร์ดยังรันเฟิร์มแวร์เก่า เพราะ make program ไม่ได้เขียน flash
    2. LVGL เริ่มทำงานไม่สำเร็จ จึงแสดงหน้าจอสำรอง
    3. ตอน link ไม่มี example_main() แบบ strong จากไฟล์ของ episode จึงใช้ตัว weak ใน example_main_default.c แทน ให้ตรวจว่าคัดลอกไฟล์ episode ลง proj_cm55/apps/ ถูกที่แล้ว build ใหม่
    4. ต้องเพิ่ม #include ของ episode ลงใน main.c ก่อน episode จึงจะทำงาน
    Show answer

    Answer: C. ตอน link ไม่มี example_main() แบบ strong จากไฟล์ของ episode จึงใช้ตัว weak ใน example_main_default.c แทน ให้ตรวจว่าคัดลอกไฟล์ episode ลง proj_cm55/apps/ ถูกที่แล้ว build ใหม่

    master ประกาศ example_main() แบบ weak ไว้ในไฟล์ default ซึ่งวาดการ์ดนี้และพิมพ์ [MASTER][STUB] ลง serial log เมื่อ episode ใน proj_cm55/apps/ ให้ example_main() แบบ strong ตัว linker จะเลือกของ episode แทน ไม่ต้องแก้ main.c เลย เพราะ main.c เรียก example_main(parent) เหมือนเดิมทุก episode ถ้า LVGL เริ่มไม่สำเร็จ main.c จะพิมพ์ error แล้วหยุด ไม่ใช่วาดการ์ดนี้

  3. You copy a new episode into proj_cm55/apps/ without removing the previous episode's files. What happens at build time? (Objective 2)

    1. link ล้มด้วย duplicate symbol example_main เพราะมี example_main() แบบ strong สองตัว
    2. build ผ่าน และบอร์ดรัน episode ที่ถูกคัดลอกเข้าไปทีหลัง
    3. build ผ่าน และบอร์ดรันทั้งสอง episode สลับกัน
    4. compiler เลือก episode ที่ชื่อไฟล์เรียงก่อนให้เอง
    Show answer

    Answer: A. link ล้มด้วย duplicate symbol example_main เพราะมี example_main() แบบ strong สองตัว

    EXTENDING.md ข้อ 4.4: ใน build หนึ่งมี example_main() แบบ strong ได้ตัวเดียว ถ้ามีสองตัว linker จะแจ้ง duplicate symbol จึงต้องล้างไฟล์ของ episode เก่าก่อน (คำสั่ง find … -exec rm ใน README ทำขั้นนี้) แล้วค่อยวาง episode ใหม่

  4. Put in order what happens from power-on until the episode code starts. (Objective 3)

    1. proj_cm55 สร้าง cm55_gfx_task แล้วเริ่ม FreeRTOS scheduler
    2. proj_cm33_s เตรียมบอร์ดแล้วกระโดดไปยังโปรแกรมฝั่ง non-secure
    3. cm55_gfx_task เตรียม VGLite, LVGL, จอ และ touch แล้วเรียก example_main(parent)
    4. proj_cm33_ns เปิดคอร์ CM55 ที่ boot address ของมัน แล้วเข้า deep sleep
    Show answer

    Correct order: B. proj_cm33_s เตรียมบอร์ดแล้วกระโดดไปยังโปรแกรมฝั่ง non-secure → D. proj_cm33_ns เปิดคอร์ CM55 ที่ boot address ของมัน แล้วเข้า deep sleep → A. proj_cm55 สร้าง cm55_gfx_task แล้วเริ่ม FreeRTOS scheduler → C. cm55_gfx_task เตรียม VGLite, LVGL, จอ และ touch แล้วเรียก example_main(parent)

    ดูจาก main.c ของทั้งสามโปรเจกต์ที่ commit 082fd3e: proj_cm33_s เรียก cybsp_init() แล้วเรียก reset handler ของฝั่ง non-secure, proj_cm33_ns เรียก Cy_SysEnableCM55() แล้ววน deep sleep, proj_cm55 สร้าง task กราฟิกแล้วเริ่ม scheduler และ task นั้นเรียก example_main() ครั้งเดียวหลังทุกอย่างพร้อม

  5. In this course's master template, which project runs the episode's LVGL work, sensor drivers and the Wi-Fi stack (cy_wcm, lwIP, mbedTLS)? (Objective 3)

    1. proj_cm33_s เพราะงานเครือข่ายต้องอยู่ฝั่ง secure
    2. แยกกัน: LVGL อยู่ proj_cm55 ส่วน Wi-Fi อยู่ proj_cm33_ns
    3. proj_cm33_ns เพราะ CM33 เป็นคอร์หลักของชิป
    4. proj_cm55 ทั้งหมด ส่วน proj_cm33_ns แค่ปลุก CM55 แล้วหลับ
    Show answer

    Answer: D. proj_cm55 ทั้งหมด ส่วน proj_cm33_ns แค่ปลุก CM55 แล้วหลับ

    โฟลเดอร์ deps ของ proj_cm55 มีทั้ง lvgl, freertos, ไดรเวอร์เซนเซอร์ และ wifi-core-freertos-lwip-mbedtls ส่วน proj_cm33_ns ไม่มี library ใดเลย main.c ของมันแค่เปิด CM55 แล้วเข้า deep sleep ตัวอย่าง Wi-Fi ในโมดูล 2 ก็เรียก cy_wcm จากโค้ดใน proj_cm55/apps/ โปรเจกต์อื่นนอก master template อาจแบ่งงานต่างไป (ตัวอย่าง OPTIGA ในโมดูล 5 วางแอปไว้ที่ proj_cm33_ns) จึงต้องดูจาก deps และ main.c ของแต่ละโปรเจกต์เสมอ

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"Toolchain, board and the master template" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

Thai attribution: "เครื่องมือ บอร์ด และ master template" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/tesaiot-firmware-stack/m01-getting-started/l01-toolchain-and-master-template/

This lesson adapts the source below; keep its credit too.
https://github.com/tesaiot/developer-hub/blob/082fd3e76595b62cfdb213499a093c233dbb4b53/README.md · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.

Full guide: how to cite TESA

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA